﻿using System;
using System.Configuration;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using TSharp.Core; 
using TSharp.Core.Caching;
using System.Web.Caching;
namespace TSharp.Core.Util.Security
{
    /// <summary>
    /// RSA加密类，可以进行加密大数据和进行签名
    /// </summary>
    /// <remarks>
    /// RSA会产生一对密钥，其中私钥用于解密数据和对数据进行签名，而公钥一般对外分发，用于加密数据或验证签名。
    /// </remarks>
    public class RSAUtil : IDisposable
    {
        private readonly RSACryptoServiceProvider rsa;
        private readonly Encoding encoding = Encoding.UTF8;

        #region ctor

        static RSAUtil()
        {
            string v;
            //公钥文件
            v = ConfigurationManager.AppSettings.Get(CONFIGKEY_PUBLICKEYFILE);
            if (string.IsNullOrEmpty(v))
                v = "~/config/publickey.config";
            DefaultPublicKeyFilePath = MapPath(v);
            ;
            //私钥文件
            v = ConfigurationManager.AppSettings.Get(CONFIGKEY_PRIVATEKEYFILE);
            if (string.IsNullOrEmpty(v))
                v = "~/config/privatekey.config";
            DefaultPrivateKeyFilePath = MapPath(v);
        }

        /// <summary>
        /// RSA加密类，可以进行加密大数据和进行签名
        /// </summary>
        public RSAUtil()
        {
            rsa = new RSACryptoServiceProvider();
        }
        /// <summary>
        /// 使用指定字符集编码数据
        /// </summary>
        /// <param name="encoding">The encoding.</param>
        public RSAUtil(Encoding encoding)
            : this()
        {
            this.encoding = encoding;
        }

        /// <summary>
        /// RSA加密类，可以进行加密大数据和进行签名
        /// </summary>
        /// <param name="keyfile">密钥文件名</param>
        public RSAUtil(string keyfile)
            : this()
        {
            LoadKeyFromXmlFile(keyfile);
        }

        #endregion

        #region 设置密钥


        /// <summary>
        /// 密钥对(公钥&amp;私钥)
        /// </summary>
        public string XmlPrivateKey
        {
            get { return rsa.ToXmlString(true); }
            set { rsa.FromXmlString(value); }
        }

        /// <summary>
        /// 公钥
        /// </summary>
        public string XmlPublicKey
        {
            get { return rsa.ToXmlString(false); }
            set { rsa.FromXmlString(value); }
        }

        /// <summary>
        /// 根据类型（需强命名）设置公钥
        /// </summary>
        /// <param name="type"></param>
        public RSAUtil SetPublicKey(Type type)
        {
            return SetPublicKey(Assembly.GetAssembly(type));
        }

        /// <summary>
        /// 从强命名程序设置公钥
        /// </summary>
        /// <param name="assembly"></param>
        public RSAUtil SetPublicKey(Assembly assembly)
        {
            rsa.ImportCspBlob(GetCspBlobAssembly(assembly, true));
            rsa.FromXmlString(rsa.ToXmlString(false));
            return this;
        }

        /// <summary>
        /// 从强签名程序集读取公钥
        /// </summary>
        /// <param name="assembly"></param>
        /// <param name="doChangeAlgId"></param>
        /// <returns></returns>
        public static byte[] GetCspBlobAssembly(Assembly assembly, bool doChangeAlgId)
        {
            byte[] publicKeyAssembly = assembly.GetName().GetPublicKey();
            int size = publicKeyAssembly.Length - 12;
            var retorno = new byte[size];
            Buffer.BlockCopy(publicKeyAssembly, 12, retorno, 0, size);
            if (doChangeAlgId)
                retorno[5] = (byte)(0x80 | retorno[5]);
            return retorno;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="contents"></param>
        /// <returns></returns>
        private bool SaveToFile(string filename, string contents)
        {
            try
            {
                File.WriteAllText(filename, contents);
                return true;
            }
            catch
            {
            }
            return false;
        }

        /// <summary>
        /// 读取文件返回文件内容，并放入缓存以备下次读取（100分钟有效）
        /// </summary>
        /// <param name="filename">The filename.</param>
        /// <returns></returns>
        private string LoadFromFile(string filename)
        {
            try
            {
                return CacheUtil.GetOrAdd<string>(filename, key => File.ReadAllText(filename)
                , TimeSpan.FromDays(100), (k, v) => new CacheDependency(filename), null);

            }
            catch
            {
#if DEBUG
                throw;
#else
    //为避免暴露密钥配置路径重置异常
                throw new Exception("密钥无效，请设置正确密钥。");
#endif
            }
        }

        /// <summary>
        /// 载入密钥文件
        /// </summary>
        /// <param name="xmlfilename">密钥文件(key.xml or publickey.xml)</param>
        public RSAUtil LoadKeyFromXmlFile(string xmlfilename)
        {
            rsa.FromXmlString(LoadFromFile(xmlfilename));
            return this;
        }

        /// <summary>
        /// 从pfx文件加载私钥
        /// </summary>
        /// <param name="pfxFileName">pfx文件路径</param>
        /// <param name="pwd"></param>
        /// <returns></returns>
        public RSAUtil LoadKeyFromPfxFile(string pfxFileName, string pwd)
        {
            var pc = new X509Certificate2(pfxFileName, pwd, X509KeyStorageFlags.Exportable);
            var r = (RSACryptoServiceProvider)pc.PrivateKey;
            rsa.FromXmlString(r.ToXmlString(true));
            return this;
        }

        /// <summary>
        /// 从cer文件加载公钥
        /// </summary>
        /// <param name="cerFileName">cer文件路径</param>
        /// <returns></returns>
        public RSAUtil LoadKeyFromCerFile(string cerFileName)
        {
            var pc = new X509Certificate2(cerFileName);
            var r = (RSACryptoServiceProvider)pc.PrivateKey;
            rsa.FromXmlString(r.ToXmlString(true));
            return this;
        }

        /// <summary>
        /// 保存密钥到XML文件
        /// </summary>
        public void SaveKeyToFile()
        {
            SaveToFile("key.xml", XmlPrivateKey);
            SaveToFile("PublicKey.xml", XmlPublicKey);
        }

        /// <summary>
        /// 保存密钥到XML文件
        /// </summary>
        /// <param name="directoryPath">保存路径</param>
        public void SaveKeyToFile(string directoryPath)
        {
            if (!Directory.Exists(directoryPath)) Directory.CreateDirectory(directoryPath);
            SaveToFile(Path.Combine(directoryPath, "key.xml"), XmlPrivateKey);
            SaveToFile(Path.Combine(directoryPath, "PublicKey.xml"), XmlPublicKey);
        }

        #endregion

        #region 加密

        /// <summary>
        /// 使用公钥进行加密
        /// </summary>
        /// <param name="rgb">待加密Byte数组</param>
        /// <returns>加密后的Byte数组</returns>
        public byte[] EncryptData(byte[] rgb)
        {
            return rsa.Encrypt(rgb, false);
        }

        /// <summary>
        /// 使用公钥进行加密
        /// </summary>
        /// <param name="source">待加密的字符串</param>
        /// <returns>经Base64转换的加密字符串</returns>
        public string Encrypt(string source)
        {
            return Convert.ToBase64String(EncryptData(encoding.GetBytes(source)));
        }

        /// <summary>
        /// 使用指定公钥加密
        /// </summary>
        /// <param name="xmlPublicKey"></param>
        /// <param name="source"></param>
        /// <returns></returns>
        public string Encrypt(string xmlPublicKey, string source)
        {
            XmlPublicKey = xmlPublicKey;
            return Encrypt(source);
        }

        #endregion

        #region 解密

        /// <summary>
        /// 使用私钥进行解密
        /// </summary>
        /// <param name="rgb">待解密的Byte数组</param>
        /// <returns>解密后的Byte数组</returns>
        public byte[] DecryptData(byte[] rgb)
        {
            rgb.IsNull("代解密数组不能为null。");
            return rsa.Decrypt(rgb, false);
        }

        /// <summary>
        /// 使用私钥进行解密
        /// </summary>
        /// <param name="base64Contents">待解密的Base64加密字符串</param>
        /// <returns>解密后的字符串</returns>
        public string Decrypt(string base64Contents)
        {
            byte[] data = Convert.FromBase64String(base64Contents);
            return encoding.GetString(DecryptData(data));
        }

        /// <summary>
        /// 使用指定私钥进行解密
        /// </summary>
        /// <param name="xmlPrivateKey">xml格式私钥</param>
        /// <param name="base64Contents"></param>
        /// <returns></returns>
        public string Decrypt(string xmlPrivateKey, string base64Contents)
        {
            XmlPrivateKey = xmlPrivateKey;
            return Decrypt(base64Contents);
        }

        #endregion

        #region 签名

        private readonly object _hashProvider = new MD5CryptoServiceProvider();

        /// <summary>
        /// Gets the hash provider.（签名时hash的提供器）
        /// </summary>
        /// <value>The hash provider.</value>
        public object HashProvider
        {
            get
            {
                //if (_hashProvider == null)
                //{
                //    //  var tmp = new SHA1CryptoServiceProvider();
                //    // var tmp = new MD5Cng();
                //    var tmp = new MD5CryptoServiceProvider();
                //    tmp.
                //    _hashProvider = tmp;
                //}
                return _hashProvider;
            }
        }

        /// <summary>
        /// 使用私钥进行签名
        /// </summary>
        /// <param name="data">待签名的Byte数组</param>
        /// <returns>签名Byte数组</returns>
        public byte[] SignData(byte[] data)
        {
            return rsa.SignData(data, HashProvider);
        }

        /// <summary>
        /// 使用私钥签名文件流
        /// </summary>
        /// <param name="inputStream"></param>
        /// <returns></returns>
        public byte[] SignData(Stream inputStream)
        {
            return rsa.SignData(inputStream, HashProvider);
        }

        // new SHA1CryptoServiceProvider();
        /// <summary>
        /// 使用私钥进行签名
        /// </summary>
        /// <param name="data">待签名的字符串</param>
        /// <returns>经Base64转换的签名字符串</returns>
        public string Sign(string data)
        {
            return Convert.ToBase64String(SignData(encoding.GetBytes(data)));
        }

        #endregion

        #region 验证签名

        /// <summary>
        /// 使用公钥验证签名
        /// </summary>
        /// <param name="sourceData">待验证的Byte数组</param>
        /// <param name="sign">签名Byte数组</param>
        /// <returns>true: 签名正确; false: 签名错误。</returns>
        public bool VerifyData(byte[] sourceData, byte[] sign)
        {
            return rsa.VerifyData(sourceData, HashProvider, sign);
        }

        /// <summary>
        /// 使用公钥验证签名
        /// </summary>
        /// <param name="data">待验证的字符串</param>
        /// <param name="signBase64String">经Base64转换的签名字符串</param>
        /// <returns>true: 签名正确; false: 签名错误。</returns>
        public bool Verify(string data, string signBase64String)
        {
            return VerifyData(encoding.GetBytes(data), Convert.FromBase64String(signBase64String));
        }

        #endregion



        /// <summary>
        /// 使用指定公钥加密长数据
        /// </summary>
        /// <param name="xmlpublickey"></param>
        /// <param name="sourceLongData"></param>
        /// <returns></returns>
        public String EncryptLongData(string xmlpublickey, string sourceLongData)
        {
            XmlPublicKey = xmlpublickey;
            return EncryptLongData(sourceLongData);
        }

        /// <summary>
        /// 根据提供的公钥，加密明文sourceLongData
        /// </summary>
        /// <param name="sourceLongData"></param>
        /// <returns></returns>
        public String EncryptLongData(string sourceLongData)
        {
            sourceLongData.IsNull("sourceLongData不能为null。");
            return Convert.ToBase64String(EncryptLongData(encoding.GetBytes(sourceLongData)));
        }
        private static readonly RNGCryptoServiceProvider rngProvider = new RNGCryptoServiceProvider();
        /// <summary>
        /// 根据提供的公钥，加密明文sourceLongData
        /// </summary>    
        /// <param name="sourceLongData">明文</param>
        /// <returns>密文byte[]</returns>
        public byte[] EncryptLongData(byte[] sourceLongData)
        {
            sourceLongData.IsNull("加密数据不能为null。");

            #region 生成TripleDES算法的key和IV，用来加密明文大数据
            var tripleDESKey = new byte[24];
            rngProvider.GetBytes(tripleDESKey);
            var tripleDESIV = new byte[8];
            rngProvider.GetBytes(tripleDESIV);

            #endregion

            #region 使用RSA公钥加密TripleDES算法的key和IV

            //使用RSA公钥加密TripleDES算法的key和IV
            byte[] Key_IV = tripleDESIV.Append(tripleDESKey);
            //用RSA公钥加密TripleDES算法的key和IV
            byte[] Key_IV_Encrypted = EncryptData(Key_IV);

            #endregion

            #region 用TripleDES算法的key和IV加密明文数据

            byte[] data = SymmetryUtil.Encrypt(sourceLongData, tripleDESKey, tripleDESIV);
            return Key_IV_Encrypted.Append(data);

            #endregion
        }

        /// <summary>
        /// 使用指定私钥解密大数据
        /// </summary>
        /// <param name="xmlPrivateKey"></param>
        /// <param name="base64EncryptData"></param>
        /// <returns></returns>
        public string DecryptLongData(string xmlPrivateKey, string base64EncryptData)
        {
            XmlPrivateKey = xmlPrivateKey;
            return DecryptLongData(base64EncryptData);
        }

        /// <summary>
        /// 根据私钥，解密base64EncryptData
        /// </summary>
        /// <param name="base64EncryptData"></param>
        /// <returns></returns>
        public string DecryptLongData(string base64EncryptData)
        {
            byte[] data;
            try
            {
                data = Convert.FromBase64String(base64EncryptData);
            }
            catch (Exception ex)
            {
                throw new Exception("解密数据必须是base64编码的字符串。", ex);
            }
            return encoding.GetString(DecryptLongData(data));
        }

        /// <summary>
        /// 根据私钥，解密明文cryptographic
        /// </summary>       
        /// <param name="cryptographic">密文byte[]</param>
        /// <returns>密文byte[]</returns>
        public byte[] DecryptLongData(byte[] cryptographic)
        {
            int split = rsa.KeySize / 8;
            //分拆加密的长数据和加密的对称密钥
            byte[] ivAndkeyEncrypted = cryptographic.SubByte(0, split);
            byte[] longDataBytesEncrypted = cryptographic.SubByte(split, cryptographic.LongLength - split);

            #region 解密TripleDES算法的key和IV

            byte[] ivAndkey = DecryptData(ivAndkeyEncrypted);
            //把key从base64字符串复原为byte[]的原始形式
            byte[] keyArray = ivAndkey.SubByte(8, ivAndkey.Length - 8);
            //把IV从base64字符串复原为byte[]的原始形式
            byte[] IVArray = ivAndkey.SubByte(0, 8);

            #endregion

            #region 使用解密后获得的TripleDES密钥解密长数据密文

            return SymmetryUtil.Decrypt(longDataBytesEncrypted, keyArray, IVArray);

            #endregion
        }

        #region ~cotr

        /// <summary>
        /// 执行与释放或重置非托管资源相关的应用程序定义的任务。
        /// </summary>
        public void Dispose()
        {
            var s = rsa as IDisposable;
            if (s != null)
                s.Dispose();
        }

        #endregion

        #region 默认密钥文件

        /// <summary>
        /// 公钥位置
        /// </summary>
        private static readonly string DefaultPublicKeyFilePath;

        /// <summary>
        /// 私钥位置
        /// </summary>
        private static readonly string DefaultPrivateKeyFilePath;

        /// <summary>
        /// 签名公钥配置标志（AppSetting）
        /// </summary>
        public static String CONFIGKEY_PUBLICKEYFILE = "PublicKeyXmlFile";

        /// <summary>
        /// 签名私钥配置标识（AppSetting）
        /// </summary>
        public static String CONFIGKEY_PRIVATEKEYFILE = "PrivateKeyXmlFile";

        /// <summary>
        /// 根据虚拟路径获取文件绝对路径，相当于Server.MapPath("");
        /// <para>
        /// 需要修改 不支持"../../t.mdb"的情况 @tangjingbo @2010-07-23
        /// </para>
        /// </summary>
        /// <param name="path">Name of the relative file.</param>
        /// <returns></returns>
        public static string MapPath(string path)
        {
            if (Path.IsPathRooted(path))
                return path;
            //  var rootpath = HttpRuntime.AppDomainAppPath;
            path = path.Replace('/', '\\'); // replace / with \
            if (path.StartsWith("~\\"))
                path = path.Substring(2);
            else if (path.StartsWith("\\"))
                path = path.Substring(1);
            //应用程序根路径，不能使用HttpRuntime.AppDomainAppPath ,web下可用，但winform会报错
            string appbasePath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
            return Path.Combine(appbasePath, path);
        }

        /// <summary>
        /// Uses the default public key file.（webconfig配置中的）
        /// </summary>
        /// <returns></returns>
        public RSAUtil UseDefaultPublicKeyFile()
        {
            if (File.Exists(DefaultPublicKeyFilePath))
                LoadKeyFromXmlFile(DefaultPublicKeyFilePath);
            else
                throw new FileNotFoundException("文件不存在", DefaultPublicKeyFilePath);
            return this;
        }

        /// <summary>
        /// 使用配置中默认的私钥
        /// </summary>
        /// <returns></returns>
        public RSAUtil UseDefaultPrivateKeyFile()
        {
            if (File.Exists(DefaultPrivateKeyFilePath))
                LoadKeyFromXmlFile(DefaultPrivateKeyFilePath);
            else
                throw new FileNotFoundException("文件不存在", DefaultPrivateKeyFilePath);
            return this;
        }

        #endregion
    }
}